Coverage Report

Created: 2024-12-26 12:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
D:\a\tools.proto\tools.proto\compiler\src\gen\template\core.rs
Line
Count
Source
1
// Copyright (c) 2024, BlockProject 3D
2
//
3
// All rights reserved.
4
//
5
// Redistribution and use in source and binary forms, with or without modification,
6
// are permitted provided that the following conditions are met:
7
//
8
//     * Redistributions of source code must retain the above copyright notice,
9
//       this list of conditions and the following disclaimer.
10
//     * Redistributions in binary form must reproduce the above copyright notice,
11
//       this list of conditions and the following disclaimer in the documentation
12
//       and/or other materials provided with the distribution.
13
//     * Neither the name of BlockProject 3D nor the names of its contributors
14
//       may be used to endorse or promote products derived from this software
15
//       without specific prior written permission.
16
//
17
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
21
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29
use crate::gen::codec::CodecMap;
30
use crate::gen::template::options::Options;
31
use crate::gen::template::parse_tree::{Component, Fragment, FragmentMode, Token};
32
use crate::gen::template::Error;
33
use itertools::Itertools;
34
use std::borrow::Cow;
35
use std::collections::HashMap;
36
37
pub struct Template<'fragment, 'variable> {
38
    fragments: HashMap<String, Fragment<'fragment>>,
39
    variables: HashMap<&'variable str, Cow<'variable, str>>,
40
}
41
42
impl<'fragment, 'variable> Template<'fragment, 'variable> {
43
150
    pub fn compile(data: &'fragment [u8]) -> Result<Self, Error> {
44
150
        Self::compile_with_options(data, &Options::default())
45
150
    }
46
47
396
    pub fn compile_with_options(data: &'fragment [u8], options: &Options) -> Result<Self, Error> {
48
396
        Self::compile_with_options_includes(data, &CodecMap::default(), options)
49
396
    }
50
51
10
    pub fn compile_with_includes(data: &'fragment [u8], codecs: &CodecMap<'fragment, '_>) -> Result<Self, Error> {
52
10
        Self::compile_with_options_includes(data, codecs, &Options::default())
53
10
    }
54
55
406
    pub fn compile_with_options_includes(
56
406
        data: &'fragment [u8],
57
406
        codecs: &CodecMap<'fragment, '_>,
58
406
        options: &Options,
59
406
    ) -> Result<Self, Error> {
60
406
        let mut fragments = HashMap::new();
61
406
        let mut frag_stack = Vec::new();
62
1.13M
        let lines = data.split(|v| *v == b'\n'
)406
;
63
35.4k
        for 
line35.0k
in lines {
64
35.0k
            if line.is_empty() {
  Branch (64:16): [True: 285, False: 34.6k]
  Branch (64:16): [True: 1, False: 176]
65
286
                continue;
66
34.7k
            }
67
34.7k
            let line = if line[line.len() - 1] == b'\r' {
  Branch (67:27): [True: 34.4k, False: 120]
  Branch (67:27): [True: 176, False: 0]
68
34.6k
                &line[..line.len() - 1]
69
            } else {
70
120
                line
71
            };
72
34.7k
            if line.is_empty() {
  Branch (72:16): [True: 1.97k, False: 32.6k]
  Branch (72:16): [True: 19, False: 157]
73
1.99k
                continue;
74
32.7k
            }
75
32.7k
            if line.starts_with(b"#include ") && 
frag_stack.is_empty()10
{
  Branch (75:16): [True: 10, False: 32.6k]
  Branch (75:50): [True: 10, False: 0]
  Branch (75:16): [True: 0, False: 157]
  Branch (75:50): [True: 0, False: 0]
76
10
                let name = std::str::from_utf8(&line[9..]).map_err(|_| 
Error::InvalidUTF80
)
?0
;
77
10
                let template = codecs.get(name).ok_or_else(|| 
Error::IncludeNotFound(name.into())0
)
?0
;
78
60
                
fragments.extend(template.fragments.iter().map(10
|(k, v)| (k.clone(), v.clone())));
79
32.7k
            } else if line.starts_with(b"#fragment push ") {
  Branch (79:23): [True: 4.63k, False: 27.9k]
  Branch (79:23): [True: 8, False: 149]
80
4.64k
                let fragment = std::str::from_utf8(&line[15..]).map_err(|_| 
Error::InvalidUTF80
)
?0
;
81
4.64k
                if let Some(
id169
) = fragment.find(":") {
  Branch (81:24): [True: 169, False: 4.46k]
  Branch (81:24): [True: 0, False: 8]
82
169
                    let name = &fragment[..id];
83
169
                    let mode = &fragment[id + 1..];
84
169
                    frag_stack.push(Fragment {
85
169
                        name,
86
169
                        content: Vec::new(),
87
169
                        mode: FragmentMode::from_str(mode).ok_or_else(|| 
Error::UnknownFragmentMode(mode.into())0
)
?0
,
88
                    });
89
4.47k
                } else {
90
4.47k
                    frag_stack.push(Fragment {
91
4.47k
                        name: fragment,
92
4.47k
                        content: Vec::new(),
93
4.47k
                        mode: FragmentMode::Default,
94
4.47k
                    });
95
4.47k
                }
96
28.1k
            } else if line.starts_with(b"#fragment pop") {
  Branch (96:23): [True: 4.63k, False: 23.3k]
  Branch (96:23): [True: 8, False: 141]
97
4.64k
                if frag_stack.is_empty() {
  Branch (97:20): [True: 0, False: 4.63k]
  Branch (97:20): [True: 0, False: 8]
98
0
                    return Err(Error::InvalidPop);
99
4.64k
                }
100
8.71k
                let combined_name = frag_stack.iter().map(|v| v.name).join(".");
101
4.64k
                //SAFETY: this is fine because the fragment stack must not be empty at this point.
102
4.64k
                let mut fragment = unsafe { frag_stack.pop().unwrap_unchecked() };
103
4.64k
                if options.is_fragment_disabled(&combined_name) {
  Branch (103:20): [True: 4, False: 4.63k]
  Branch (103:20): [True: 0, False: 8]
104
4
                    fragment.content.clear();
105
4.64k
                }
106
4.64k
                fragments.insert(combined_name, fragment);
107
            } else {
108
23.4k
                let cur_fragment = frag_stack.last_mut().ok_or(Error::NoFragment)
?0
;
109
23.4k
                let mut token = Token::new(line);
110
901k
                while token.has_next() {
  Branch (110:23): [True: 872k, False: 23.3k]
  Branch (110:23): [True: 5.19k, False: 141]
111
877k
                    if token.cur() == b'{' {
  Branch (111:24): [True: 24.0k, False: 848k]
  Branch (111:24): [True: 90, False: 5.10k]
112
24.1k
                        if token.next() == Some(b'{') {
  Branch (112:28): [True: 5.84k, False: 18.2k]
  Branch (112:28): [True: 44, False: 46]
113
5.89k
                            token.inc();
114
18.2k
                        }
115
24.1k
                        if let Some(
component22.5k
) = token.pop()
?0
.map(Component::Constant) {
  Branch (115:32): [True: 22.4k, False: 1.63k]
  Branch (115:32): [True: 90, False: 0]
116
22.5k
                            cur_fragment.content.push(component);
117
22.5k
                        
}1.63k
118
853k
                    } else if token.cur() == b'}' {
  Branch (118:31): [True: 24.0k, False: 824k]
  Branch (118:31): [True: 90, False: 5.01k]
119
24.1k
                        if token.next() == Some(b'}') {
  Branch (119:28): [True: 5.84k, False: 18.2k]
  Branch (119:28): [True: 44, False: 46]
120
5.89k
                            token.inc();
121
5.89k
                            if let Some(component) = token.pop()
?0
.map(Component::Constant) {
  Branch (121:36): [True: 5.84k, False: 0]
  Branch (121:36): [True: 44, False: 0]
122
5.89k
                                cur_fragment.content.push(component);
123
5.89k
                            
}0
124
18.2k
                        } else if let Some(component) =
  Branch (124:39): [True: 18.2k, False: 0]
  Branch (124:39): [True: 46, False: 0]
125
18.2k
                            token.pop()
?0
.map(|v| Component::parse_variable(options.functions(), v))
126
                        {
127
18.2k
                            cur_fragment.content.push(component
?0
);
128
0
                        }
129
829k
                    }
130
877k
                    token.inc()
131
                }
132
23.4k
                if let Some(
component10.8k
) = token.pop()
?0
.map(Component::Constant) {
  Branch (132:24): [True: 10.7k, False: 12.5k]
  Branch (132:24): [True: 63, False: 78]
133
10.8k
                    cur_fragment.content.push(component);
134
12.6k
                }
135
23.4k
                cur_fragment.content.push(Component::NewLine);
136
            }
137
        }
138
406
        Ok(Template {
139
406
            fragments,
140
406
            variables: HashMap::new(),
141
406
        })
142
406
    }
143
144
1.21k
    pub fn var(&mut self, key: &'variable str, value: impl Into<Cow<'variable, str>>) -> &mut Self {
145
1.21k
        self.variables.insert(key, value.into());
146
1.21k
        self
147
1.21k
    }
148
149
66
    pub fn var_d(&mut self, key: &'variable str, value: impl ToString) -> &mut Self {
150
66
        self.variables.insert(key, value.to_string().into());
151
66
        self
152
66
    }
153
154
2.50k
    fn render_internal(
155
2.50k
        &self,
156
2.50k
        variables: &HashMap<&str, Cow<str>>,
157
2.50k
        path: &str,
158
2.50k
        fragments: &[&str],
159
2.50k
    ) -> Result<String, Error> {
160
2.50k
        let mut rendered = Vec::new();
161
5.25k
        for 
name2.74k
in fragments {
162
2.74k
            let name: Cow<str> = match path.is_empty() {
163
1.78k
                false => Cow::Owned(format!("{}.{}", path, name)),
164
959
                true => Cow::Borrowed(name),
165
            };
166
2.74k
            let fragment = self.fragments.get(&*name).ok_or_else(|| 
Error::FragmentNotFound(String::from(&*name))0
)
?0
;
167
2.74k
            let sub_rendered = fragment
168
2.74k
                .content
169
2.74k
                .iter()
170
50.4k
                .map(|v| match v {
171
24.0k
                    Component::Constant(v) => Ok(Cow::Borrowed(*v)),
172
10.2k
                    Component::Variable(v) => {
173
10.2k
                        let variable = variables.get(v.name).ok_or_else(|| 
Error::VariableNotFound(v.name.into())0
)
?0
;
174
10.2k
                        if let Some(
function136
) = v.function {
  Branch (174:32): [True: 136, False: 10.0k]
  Branch (174:32): [True: 0, False: 4]
175
136
                            let variable = function(variable);
176
136
                            Ok(variable)
177
                        } else {
178
10.1k
                            Ok(Cow::Borrowed(&**variable))
179
                        }
180
                    }
181
16.1k
                    Component::NewLine => Ok(Cow::Borrowed("\n")),
182
50.4k
                })
183
2.74k
                .collect::<Result<Vec<Cow<str>>, Error>>()
?0
184
2.74k
                .join("");
185
2.74k
            if fragment.mode == FragmentMode::Inline {
  Branch (185:16): [True: 191, False: 2.55k]
  Branch (185:16): [True: 0, False: 1]
186
191
                rendered.push(sub_rendered.trim().into());
187
2.55k
            } else {
188
2.55k
                rendered.push(sub_rendered);
189
2.55k
            }
190
        }
191
2.50k
        Ok(rendered.join(""))
192
2.50k
    }
193
194
1.70k
    pub fn scope(&self) -> Scope {
195
1.70k
        Scope {
196
1.70k
            template: self,
197
1.70k
            variables: self.variables.clone(),
198
1.70k
        }
199
1.70k
    }
200
201
487
    pub fn render(&self, path: &str, fragments: &[&str]) -> Result<String, Error> {
202
487
        self.render_internal(&self.variables, path, fragments)
203
487
    }
204
}
205
206
#[derive(Clone)]
207
pub struct Scope<'a, 'fragment, 'variable> {
208
    template: &'a Template<'fragment, 'variable>,
209
    variables: HashMap<&'variable str, Cow<'variable, str>>,
210
}
211
212
impl<'variable> Scope<'_, '_, 'variable> {
213
4.59k
    pub fn var(&mut self, key: &'variable str, value: impl Into<Cow<'variable, str>>) -> &mut Self {
214
4.59k
        self.variables.insert(key, value.into());
215
4.59k
        self
216
4.59k
    }
217
218
3.23k
    pub fn var_d(&mut self, key: &'variable str, value: impl ToString) -> &mut Self {
219
3.23k
        self.variables.insert(key, value.to_string().into());
220
3.23k
        self
221
3.23k
    }
222
223
413
    pub fn render_to_var(&mut self, path: &str, fragments: &[&str], key: &'variable str) -> Result<&mut Self, Error> {
224
413
        let str = self.template.render_internal(&self.variables, path, fragments)
?0
;
225
413
        self.variables.insert(key, str.into());
226
413
        Ok(self)
227
413
    }
228
229
1.60k
    pub fn render(&self, path: &str, fragments: &[&str]) -> Result<String, Error> {
230
1.60k
        self.template.render_internal(&self.variables, path, fragments)
231
1.60k
    }
232
}